#pragma once

#include "util.hpp"
#include <cstdio>
#include <cassert>
#include <mutex>

class User_Info_Table
{
public:
    User_Info_Table(const std::string &host,
        uint16_t port,
        const std::string &user,
        const std::string &password,
        const std::string &dbname)
    {
        _mysql = mysql_util::mysql_init_handle(host, port, user, password, dbname);
        assert(_mysql != NULL);
    }
    ~User_Info_Table() { if(_mysql) mysql_util::mysql_destory(_mysql); }

    bool set_all_offline()
    {
#define OFFLINE "update user_info set status='离线';"
        bool ret = mysql_util::mysql_exe(_mysql, OFFLINE);
        if(!ret) {
            debug("set_all_offline fail");
            return false;
        }
        debug("set_all_offline success");
        return true;
    }
    
    bool is_exists(const std::string &username)
    {
#define CHECK "select username from user_info where username='%s';"
        char sql[4096] = {0};
        sprintf(sql, CHECK, username.c_str());
        mysql_util::mysql_exe(_mysql, sql);

        MYSQL_RES *res = nullptr;
        {// 对查询语句进行加锁保护
            // 查询成功，判断数据是否只有一行，只有一行则数据正常有效，如果有多行，则查询失败，数据异常无效
            std::unique_lock<std::mutex> lock(_mutex);
            res = mysql_store_result(_mysql);
            if(res == nullptr) {
                error("%s", "user does not exist");
                return false;
            }
            int row = mysql_num_rows(res);
            if(row != 1) {
                return false;
            }
        }
        return true;
    }

    //用户注册，向用户信息表中新增数据
    bool insert(Json::Value &userinfo)
    {
// 新增用户的sql语句
#define INSERT_USER "insert into user_info                                  \
        (username, password, ladder, rank, score, total_count, win_count)   \
        values ('%s', password('%s'), 0, 0, 0, 0, 0);"                         
        // 插入前，先判断该用户是否已经存在
        // 因为我们的username具有唯一键约束，所以如果该用户已经存在，sql语句就会执行失败
        char sql[4096] = {0};
        sprintf(sql, INSERT_USER, userinfo["username"].asCString(), userinfo["password"].asCString());
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("%s", "insert user error!!!");
            return false;
        }
        return true;
    }

    // 用户登录
    bool login(Json::Value &userinfo) 
    {
//用户登录时的查询sql语句
#define LOGIN_USER "select id, ladder, rank, score, total_count, win_count, status\
        from user_info where username='%s' and password=password('%s');"
        char sql[4096] = {0};
        sprintf(sql, LOGIN_USER, userinfo["username"].asCString(), userinfo["password"].asCString());
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("%s", "user login error!!!");
            return false;
        }

        MYSQL_RES *res = nullptr;
        {// 对查询语句进行加锁保护
            // 查询成功，判断数据是否只有一行，只有一行则数据正常有效，如果有多行，则查询失败，数据异常无效
            std::unique_lock<std::mutex> lock(_mutex);
            res = mysql_store_result(_mysql);
            if(res == nullptr) {
                error("%s", "user does not exist");
                return false;
            }
            int row = mysql_num_rows(res);
            if(row != 1) {
                error("%s", "Incorrect username or password");
                return false;
            }
            // 查询成功后，修改状态
            char sql[4096] = {0};
            sprintf(sql, "update user_info set status='在线' where username='%s'", userinfo["username"].asString().c_str());
            std::cout << "状态改变->在线" << std::endl;
            ret = mysql_util::mysql_exe(_mysql, sql);
            if(!ret) {
                error("%s", "update status error");
                return false;
            }
        }
        MYSQL_ROW one_line = mysql_fetch_row(res); // 读取该一行数据
        userinfo["id"] = std::stoi(one_line[0]);
        userinfo["ladder"] = std::stoi(one_line[1]);
        userinfo["rank"] = std::stoi(one_line[2]);
        userinfo["score"] = std::stoi(one_line[3]);
        userinfo["total_count"] = std::stoi(one_line[4]);
        userinfo["win_count"] = std::stoi(one_line[5]);
        userinfo["status"] = one_line[6];
        mysql_free_result(res);
        return true;
    }

    // 用户退出游戏，我们将用户状态设置为离线
    bool logout(int id)
    {
#define LOGOUT "update user_info set status='离线' where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, LOGOUT, id);
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("logout error");
            return false;
        }
        return true;
    }

    bool online(int id)
    {
#define ONLINE "select status from user_info where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, ONLINE, id);
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("logout error");
            return false;
        }
        MYSQL_RES *res = nullptr;
        {// 对查询语句进行加锁保护
            // 查询成功，判断数据是否只有一行，只有一行则数据正常有效，如果有多行，则查询失败，数据异常无效
            std::unique_lock<std::mutex> lock(_mutex);
            res = mysql_store_result(_mysql);
        }
        MYSQL_ROW one_line = mysql_fetch_row(res); // 读取该一行数据
        std::string status = one_line[0];
        return status == "在线";
    }
    std::string last_active(int id)
    {
#define TIME "select last_active from user_info where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, TIME, id);
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("logout error");
            return "";
        }
        MYSQL_RES *res = nullptr;
        {// 对查询语句进行加锁保护
            // 查询成功，判断数据是否只有一行，只有一行则数据正常有效，如果有多行，则查询失败，数据异常无效
            std::unique_lock<std::mutex> lock(_mutex);
            res = mysql_store_result(_mysql);
        }
        MYSQL_ROW one_line = mysql_fetch_row(res); // 读取该一行数据
        std::string time = one_line[0];
        return time;
    }

    bool alert_last_active(int id) 
    {
#define ALERT "update user_info set last_active = NOW() where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, ALERT, id);
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(ret == false) {
            error("alert last_active error");
            return false;
        }
        return true;
    }
        // 用户进入游戏，将用户状态设置为游戏中
    bool enter_game(int id)
    {
#define ENTER_GAME "update user_info set status='游戏中' where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, ENTER_GAME, id);
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("enter game error");
            return false;
        }
        return true;
    }

    // 用户退出游戏，将用户状态设置为在线
    bool exit_game(int id)
    {
#define EXIT_GAME "update user_info set status='在线' where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, EXIT_GAME, id);
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("exit game error");
            return false;
        }
        return true;
    }

    // 通过用户名查询用户信息
    bool select_by_username(const std::string &username, Json::Value &userinfo) 
    {
//通过用户查询用户信息的sql语句
#define SELECT_USER_BY_NAME "select id, ladder, rank, score, total_count, win_count, last_active, status, session_id\
        from user_info where username='%s';"
        char sql[4096] = {0};
        sprintf(sql, SELECT_USER_BY_NAME, username.c_str());
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("%s", "Failed to obtain user information through the username!!!");
            return false;
        }

        MYSQL_RES *res = nullptr;
        {// 对查询语句进行加锁保护
            // 查询成功，判断数据是否只有一行，只有一行则数据正常有效，如果有多行，则查询失败，数据异常无效
            std::unique_lock<std::mutex> lock(_mutex);
            res = mysql_store_result(_mysql);
            if(res == nullptr) {
                error("%s", "user does not exist");
                return false;
            }
            int row = mysql_num_rows(res);
            if(row != 1) {
                error("%s", "Data anomaly, user information is not unique");
                return false;
            }
        }
        MYSQL_ROW one_line = mysql_fetch_row(res); // 读取该一行数据
        userinfo["id"] = std::stoi(one_line[0]);
        userinfo["ladder"] = std::stoi(one_line[1]);
        userinfo["rank"] = std::stoi(one_line[2]);
        userinfo["score"] = std::stoi(one_line[3]);
        userinfo["total_count"] = std::stoi(one_line[4]);
        userinfo["win_count"] = std::stoi(one_line[5]);
        userinfo["last_active"] = one_line[6];
        userinfo["status"] = one_line[7];
        userinfo["session_id"] = std::stoi(one_line[8]);
        mysql_free_result(res);
        return true;
    }
    // 通过用户id查询用户信息
    bool select_by_id(const int id, Json::Value &userinfo) 
    {
//通过用户查询用户信息的sql语句
#define SELECT_USER_BY_ID "select username, ladder, rank, score, total_count, win_count, last_active, status, session_id, id\
        from user_info where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, SELECT_USER_BY_ID, id);
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("%s", "Failed to obtain user information through the id!!!");
            return false;
        }

        MYSQL_RES *res = nullptr;
        {// 对查询语句进行加锁保护
            // 查询成功，判断数据是否只有一行，只有一行则数据正常有效，如果有多行，则查询失败，数据异常无效
            std::unique_lock<std::mutex> lock(_mutex);
            res = mysql_store_result(_mysql);
            if(res == nullptr) {
                error("%s", "user does not exist");
                return false;
            }
            int row = mysql_num_rows(res);
            if(row != 1) {
                error("%s", "Data anomaly, user information is not unique");
                return false;
            }
        } 
        MYSQL_ROW one_line = mysql_fetch_row(res); // 读取该一行数据
        userinfo["username"] = one_line[0];
        userinfo["ladder"] = std::stoi(one_line[1]);
        userinfo["rank"] = std::stoi(one_line[2]);
        userinfo["score"] = std::stoi(one_line[3]);
        userinfo["total_count"] = std::stoi(one_line[4]);
        userinfo["win_count"] = std::stoi(one_line[5]);
        userinfo["last_active"] = one_line[6];
        userinfo["status"] = one_line[7];
        userinfo["session_id"] = std::stoi(one_line[8]);
        userinfo["id"] = std::stoi(one_line[9]);
        mysql_free_result(res);
        return true;
    }

    // 当玩家胜利了,我们需要拿出该用户的信息
    // 并对score进行加分(10分),如果score>50分，则上一个小段，如果此时小段超过了4，则进一个大段
    // 下面是段位的详细配置
    //  大段位ladder:黑铁1，青铜2，白银3，黄金4，铂金5，钻石6，超凡7，神话8，赋能9
    //  小段位rank:每个大段位都分有四个小段位，以神话为例，神话Ⅳ1，神话Ⅲ2，神话Ⅱ3，神话Ⅰ4，赋能段位与其他段位不同，只有分数，没有小段，且分数没有上线
    // 小段位分数socre:除赋能外，每个都只有50分
    // 当玩家刚注册，没有进行对局，这时候默认是没有段位的，即段位为0
    bool win(int id) 
    {
        // 1.先拿出该用户的信息
        Json::Value root;
        select_by_id(id, root);
        int score = root["score"].asInt();
        int rank = root["rank"].asInt();
        int ladder = root["ladder"].asInt();
        int win_count = root["win_count"].asInt();
        int total_count = root["total_count"].asInt();

        // 2.先判断玩家当前在那个大段位, 然后再更新分数
        if(ladder == 9) {
            // 赋能段位
            score += 10;
        }
        else if(ladder == 0) {
            // 没有段位
            score += 10;
            rank = 1;
            ladder = 1;
        }
        else {// 其他段位
            score += 10;
            if(score >= 50) {
                score -= 50;
                rank += 1;
                if(rank > 4) {
                    rank = 1;
                    ladder += 1;// 因为此时在其他段位，直接大段位+1即可
                    // 增加一个大段位，避免直接输一局掉下来，所有给个保底分
                    score += 5;
                }
            }
        }

        // 3.更新场次数据
        win_count++;
        total_count++;

        // 4.更新数据库中该玩家的数据
#define UPDATA_USER_WIN "update user_info set \
        ladder=%d, rank=%d, score=%d, total_count=%d, win_count=%d \
        where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, UPDATA_USER_WIN, ladder, rank, score, total_count, win_count, id);
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("%s", "Failed to update player data!!!");
            return false;
        }
        return true;
    }

    // 玩家失败，对score进行减少5，当score<0则掉一个小段，如果小段为0，则掉一个大段
    bool lose(int id) 
    {
        // 1.先拿出该用户的信息
        Json::Value root;
        select_by_id(id, root);
        int score = root["score"].asInt();
        int rank = root["rank"].asInt();
        int ladder = root["ladder"].asInt();
        int win_count = root["win_count"].asInt();
        int total_count = root["total_count"].asInt();

        // 2.先判断玩家当前在那个大段位, 然后再更新分数
        score -= 5;
        if(score < 0) {
            if(ladder == 0/*没有段位，输了一局将段位置为最低段位*/) {
                ladder = 1;
                rank = 1;
                score = 0;
            }
            if(ladder == 1 && rank == 1) {//最低段位
                ladder = 1;
                rank = 1;
                score = 0;
            }
            else {
                // 对段位进行更新
                score = 50 + score;
                rank--; //小段位--
                if(rank < 1) {//掉一个大段
                    rank = 4;
                    ladder = ladder == 1 ? ladder : ladder-1; //更新大段
                }
            }
        }

        // 3.更新场次数据
        total_count++;

        // 4.更新数据库中该玩家的数据
#define UPDATA_USER_LOSE "update user_info set \
        ladder=%d, rank=%d, score=%d, total_count=%d, win_count=%d \
        where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, UPDATA_USER_LOSE, ladder, rank, score, total_count, win_count, id);
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("%s", "Failed to update player data!!!");
            return false;
        }
        return true;
    }

    // 添加好友
    //由接收方调用该函数，创建a->b b->a的双向好友关系
    bool add_friend(int usr_id1, int usr_id2)
    {
#define ADD_FRIEND "insert into friends_info (user_id, friend_id) values (%d, %d);"
        char sql[4096] = {0};
        sprintf(sql, ADD_FRIEND, usr_id1, usr_id2);
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("%s", "Failed to add friend");
            return false;
        }
        return true;
    }

    // 查询好友
    bool select_friend_id_by_id(int usr, std::vector<int> &friends) 
    {
#define SELECT_FRIENDS "select friend_id from friends_info where user_id=%d;" 
        char sql[4096] = {0};
        sprintf(sql, SELECT_FRIENDS, usr);
        bool ret = mysql_util::mysql_exe(_mysql, sql);
        if(!ret) {
            error("Failed to select friend");
            return false;
        }

        MYSQL_RES *res = nullptr;
        {// 对查询语句进行加锁保护
            std::unique_lock<std::mutex> lock(_mutex);
            // 1.获取结果集
            res = mysql_store_result(_mysql); 
            if(res == nullptr) {
                error("%s", "user does not exist");
                return false;
            }

            // 2.将该用户所有的好友id都存储到数组中
            MYSQL_ROW row;
            while(row = mysql_fetch_row(res)) {
                friends.emplace_back(std::stoi(row[0]));
            }
            
            // 3.遍历ids数组，通过id查询用户信息，获取好友的用户名和段位等信息
            // 外部完成，避免死锁
        } 
        return true;
    }
private:
    MYSQL *_mysql;
    std::mutex _mutex;
};
